define("ace/ext/chromevox", ["require", "exports", "module", "ace/editor", "ace/config"], function (require, exports, module) {
    var cvoxAce = {};
    cvoxAce.SpeechProperty;
    cvoxAce.Cursor;
    cvoxAce.Token;
    cvoxAce.Annotation;
    var CONSTANT_PROP = {
        'rate': 0.8,
        'pitch': 0.4,
        'volume': 0.9
    };
    var DEFAULT_PROP = {
        'rate': 1,
        'pitch': 0.5,
        'volume': 0.9
    };
    var ENTITY_PROP = {
        'rate': 0.8,
        'pitch': 0.8,
        'volume': 0.9
    };
    var KEYWORD_PROP = {
        'rate': 0.8,
        'pitch': 0.3,
        'volume': 0.9
    };
    var STORAGE_PROP = {
        'rate': 0.8,
        'pitch': 0.7,
        'volume': 0.9
    };
    var VARIABLE_PROP = {
        'rate': 0.8,
        'pitch': 0.8,
        'volume': 0.9
    };
    var DELETED_PROP = {
        'punctuationEcho': 'none',
        'relativePitch': -0.6
    };
    var ERROR_EARCON = 'ALERT_NONMODAL';
    var MODE_SWITCH_EARCON = 'ALERT_MODAL';
    var NO_MATCH_EARCON = 'INVALID_KEYPRESS';
    var INSERT_MODE_STATE = 'insertMode';
    var COMMAND_MODE_STATE = 'start';

    var REPLACE_LIST = [
        {
            substr: ';',
            newSubstr: ' semicolon '
        },
        {
            substr: ':',
            newSubstr: ' colon '
        }
    ];
    var Command = {
        SPEAK_ANNOT: 'annots',
        SPEAK_ALL_ANNOTS: 'all_annots',
        TOGGLE_LOCATION: 'toggle_location',
        SPEAK_MODE: 'mode',
        SPEAK_ROW_COL: 'row_col',
        TOGGLE_DISPLACEMENT: 'toggle_displacement',
        FOCUS_TEXT: 'focus_text'
    };
    var KEY_PREFIX = 'CONTROL + SHIFT ';
    cvoxAce.editor = null;
    var lastCursor = null;
    var annotTable = {};
    var shouldSpeakRowLocation = false;
    var shouldSpeakDisplacement = false;
    var changed = false;
    var vimState = null;
    var keyCodeToShortcutMap = {};
    var cmdToShortcutMap = {};
    var getKeyShortcutString = function (keyCode) {
        return KEY_PREFIX + String.fromCharCode(keyCode);
    };
    var isVimMode = function () {
        var keyboardHandler = cvoxAce.editor.keyBinding.getKeyboardHandler();
        return keyboardHandler.$id === 'ace/keyboard/vim';
    };
    var getCurrentToken = function (cursor) {
        return cvoxAce.editor.getSession().getTokenAt(cursor.row, cursor.column + 1);
    };
    var getCurrentLine = function (cursor) {
        return cvoxAce.editor.getSession().getLine(cursor.row);
    };
    var onRowChange = function (currCursor) {
        if (annotTable[currCursor.row]) {
            cvox.Api.playEarcon(ERROR_EARCON);
        }
        if (shouldSpeakRowLocation) {
            cvox.Api.stop();
            speakChar(currCursor);
            speakTokenQueue(getCurrentToken(currCursor));
            speakLine(currCursor.row, 1);
        } else {
            speakLine(currCursor.row, 0);
        }
    };
    var isWord = function (cursor) {
        var line = getCurrentLine(cursor);
        var lineSuffix = line.substr(cursor.column - 1);
        if (cursor.column === 0) {
            lineSuffix = ' ' + line;
        }
        var firstWordRegExp = /^\W(\w+)/;
        var words = firstWordRegExp.exec(lineSuffix);
        return words !== null;
    };
    var rules = {
        'constant': {
            prop: CONSTANT_PROP
        },
        'entity': {
            prop: ENTITY_PROP
        },
        'keyword': {
            prop: KEYWORD_PROP
        },
        'storage': {
            prop: STORAGE_PROP
        },
        'variable': {
            prop: VARIABLE_PROP
        },
        'meta': {
            prop: DEFAULT_PROP,
            replace: [
                {
                    substr: '</',
                    newSubstr: ' closing tag '
                },
                {
                    substr: '/>',
                    newSubstr: ' close tag '
                },
                {
                    substr: '<',
                    newSubstr: ' tag start '
                },
                {
                    substr: '>',
                    newSubstr: ' tag end '
                }
            ]
        }
    };
    var DEFAULT_RULE = {
        prop: DEFAULT_RULE
    };
    var expand = function (value, replaceRules) {
        var newValue = value;
        for (var i = 0; i < replaceRules.length; i++) {
            var replaceRule = replaceRules[i];
            var regexp = new RegExp(replaceRule.substr, 'g');
            newValue = newValue.replace(regexp, replaceRule.newSubstr);
        }
        return newValue;
    };
    var mergeTokens = function (tokens, start, end) {
        var newToken = {};
        newToken.value = '';
        newToken.type = tokens[start].type;
        for (var j = start; j < end; j++) {
            newToken.value += tokens[j].value;
        }
        return newToken;
    };
    var mergeLikeTokens = function (tokens) {
        if (tokens.length <= 1) {
            return tokens;
        }
        var newTokens = [];
        var lastLikeIndex = 0;
        for (var i = 1; i < tokens.length; i++) {
            var lastLikeToken = tokens[lastLikeIndex];
            var currToken = tokens[i];
            if (getTokenRule(lastLikeToken) !== getTokenRule(currToken)) {
                newTokens.push(mergeTokens(tokens, lastLikeIndex, i));
                lastLikeIndex = i;
            }
        }
        newTokens.push(mergeTokens(tokens, lastLikeIndex, tokens.length));
        return newTokens;
    };
    var isRowWhiteSpace = function (row) {
        var line = cvoxAce.editor.getSession().getLine(row);
        var whiteSpaceRegexp = /^\s*$/;
        return whiteSpaceRegexp.exec(line) !== null;
    };
    var speakLine = function (row, queue) {
        var tokens = cvoxAce.editor.getSession().getTokens(row);
        if (tokens.length === 0 || isRowWhiteSpace(row)) {
            cvox.Api.playEarcon('EDITABLE_TEXT');
            return;
        }
        tokens = mergeLikeTokens(tokens);
        var firstToken = tokens[0];
        tokens = tokens.filter(function (token) {
            return token !== firstToken;
        });
        speakToken_(firstToken, queue);
        tokens.forEach(speakTokenQueue);
    };
    var speakTokenFlush = function (token) {
        speakToken_(token, 0);
    };
    var speakTokenQueue = function (token) {
        speakToken_(token, 1);
    };
    var getTokenRule = function (token) {
        if (!token || !token.type) {
            return;
        }
        var split = token.type.split('.');
        if (split.length === 0) {
            return;
        }
        var type = split[0];
        var rule = rules[type];
        if (!rule) {
            return DEFAULT_RULE;
        }
        return rule;
    };
    var speakToken_ = function (token, queue) {
        var rule = getTokenRule(token);
        var value = expand(token.value, REPLACE_LIST);
        if (rule.replace) {
            value = expand(value, rule.replace);
        }
        cvox.Api.speak(value, queue, rule.prop);
    };
    var speakChar = function (cursor) {
        var line = getCurrentLine(cursor);
        cvox.Api.speak(line[cursor.column], 1);
    };
    var speakDisplacement = function (lastCursor, currCursor) {
        var line = getCurrentLine(currCursor);
        var displace = line.substring(lastCursor.column, currCursor.column);
        displace = displace.replace(/ /g, ' space ');
        cvox.Api.speak(displace);
    };
    var speakCharOrWordOrLine = function (lastCursor, currCursor) {
        if (Math.abs(lastCursor.column - currCursor.column) !== 1) {
            var currLineLength = getCurrentLine(currCursor).length;
            if (currCursor.column === 0 || currCursor.column === currLineLength) {
                speakLine(currCursor.row, 0);
                return;
            }
            if (isWord(currCursor)) {
                cvox.Api.stop();
                speakTokenQueue(getCurrentToken(currCursor));
                return;
            }
        }
        speakChar(currCursor);
    };
    var onColumnChange = function (lastCursor, currCursor) {
        if (!cvoxAce.editor.selection.isEmpty()) {
            speakDisplacement(lastCursor, currCursor);
            cvox.Api.speak('selected', 1);
        } else if (shouldSpeakDisplacement) {
            speakDisplacement(lastCursor, currCursor);
        } else {
            speakCharOrWordOrLine(lastCursor, currCursor);
        }
    };
    var onCursorChange = function (evt) {
        if (changed) {
            changed = false;
            return;
        }
        var currCursor = cvoxAce.editor.selection.getCursor();
        if (currCursor.row !== lastCursor.row) {
            onRowChange(currCursor);
        } else {
            onColumnChange(lastCursor, currCursor);
        }
        lastCursor = currCursor;
    };
    var onSelectionChange = function (evt) {
        if (cvoxAce.editor.selection.isEmpty()) {
            cvox.Api.speak('unselected');
        }
    };
    var onChange = function (delta) {
        switch (delta.action) {
            case 'remove':
                cvox.Api.speak(delta.text, 0, DELETED_PROP);
                changed = true;
                break;
            case 'insert':
                cvox.Api.speak(delta.text, 0);
                changed = true;
                break;
        }
    };
    var isNewAnnotation = function (annot) {
        var row = annot.row;
        var col = annot.column;
        return !annotTable[row] || !annotTable[row][col];
    };
    var populateAnnotations = function (annotations) {
        annotTable = {};
        for (var i = 0; i < annotations.length; i++) {
            var annotation = annotations[i];
            var row = annotation.row;
            var col = annotation.column;
            if (!annotTable[row]) {
                annotTable[row] = {};
            }
            annotTable[row][col] = annotation;
        }
    };
    var onAnnotationChange = function (evt) {
        var annotations = cvoxAce.editor.getSession().getAnnotations();
        var newAnnotations = annotations.filter(isNewAnnotation);
        if (newAnnotations.length > 0) {
            cvox.Api.playEarcon(ERROR_EARCON);
        }
        populateAnnotations(annotations);
    };
    var speakAnnot = function (annot) {
        var annotText = annot.type + ' ' + annot.text + ' on ' +
            rowColToString(annot.row, annot.column);
        annotText = annotText.replace(';', 'semicolon');
        cvox.Api.speak(annotText, 1);
    };
    var speakAnnotsByRow = function (row) {
        var annots = annotTable[row];
        for (var col in annots) {
            speakAnnot(annots[col]);
        }
    };
    var rowColToString = function (row, col) {
        return 'row ' + (row + 1) + ' column ' + (col + 1);
    };
    var speakCurrRowAndCol = function () {
        cvox.Api.speak(rowColToString(lastCursor.row, lastCursor.column));
    };
    var speakAllAnnots = function () {
        for (var row in annotTable) {
            speakAnnotsByRow(row);
        }
    };
    var speakMode = function () {
        if (!isVimMode()) {
            return;
        }
        switch (cvoxAce.editor.keyBinding.$data.state) {
            case INSERT_MODE_STATE:
                cvox.Api.speak('Insert mode');
                break;
            case COMMAND_MODE_STATE:
                cvox.Api.speak('Command mode');
                break;
        }
    };
    var toggleSpeakRowLocation = function () {
        shouldSpeakRowLocation = !shouldSpeakRowLocation;
        if (shouldSpeakRowLocation) {
            cvox.Api.speak('Speak location on row change enabled.');
        } else {
            cvox.Api.speak('Speak location on row change disabled.');
        }
    };
    var toggleSpeakDisplacement = function () {
        shouldSpeakDisplacement = !shouldSpeakDisplacement;
        if (shouldSpeakDisplacement) {
            cvox.Api.speak('Speak displacement on column changes.');
        } else {
            cvox.Api.speak('Speak current character or word on column changes.');
        }
    };
    var onKeyDown = function (evt) {
        if (evt.ctrlKey && evt.shiftKey) {
            var shortcut = keyCodeToShortcutMap[evt.keyCode];
            if (shortcut) {
                shortcut.func();
            }
        }
    };
    var onChangeStatus = function (evt, editor) {
        if (!isVimMode()) {
            return;
        }
        var state = editor.keyBinding.$data.state;
        if (state === vimState) {
            return;
        }
        switch (state) {
            case INSERT_MODE_STATE:
                cvox.Api.playEarcon(MODE_SWITCH_EARCON);
                cvox.Api.setKeyEcho(true);
                break;
            case COMMAND_MODE_STATE:
                cvox.Api.playEarcon(MODE_SWITCH_EARCON);
                cvox.Api.setKeyEcho(false);
                break;
        }
        vimState = state;
    };
    var contextMenuHandler = function (evt) {
        var cmd = evt.detail['customCommand'];
        var shortcut = cmdToShortcutMap[cmd];
        if (shortcut) {
            shortcut.func();
            cvoxAce.editor.focus();
        }
    };
    var initContextMenu = function () {
        var ACTIONS = SHORTCUTS.map(function (shortcut) {
            return {
                desc: shortcut.desc + getKeyShortcutString(shortcut.keyCode),
                cmd: shortcut.cmd
            };
        });
        var body = document.querySelector('body');
        body.setAttribute('contextMenuActions', JSON.stringify(ACTIONS));
        body.addEventListener('ATCustomEvent', contextMenuHandler, true);
    };
    var onFindSearchbox = function (evt) {
        if (evt.match) {
            speakLine(lastCursor.row, 0);
        } else {
            cvox.Api.playEarcon(NO_MATCH_EARCON);
        }
    };
    var focus = function () {
        cvoxAce.editor.focus();
    };
    var SHORTCUTS = [
        {
            keyCode: 49,
            func: function () {
                speakAnnotsByRow(lastCursor.row);
            },
            cmd: Command.SPEAK_ANNOT,
            desc: 'Speak annotations on line'
        },
        {
            keyCode: 50,
            func: speakAllAnnots,
            cmd: Command.SPEAK_ALL_ANNOTS,
            desc: 'Speak all annotations'
        },
        {
            keyCode: 51,
            func: speakMode,
            cmd: Command.SPEAK_MODE,
            desc: 'Speak Vim mode'
        },
        {
            keyCode: 52,
            func: toggleSpeakRowLocation,
            cmd: Command.TOGGLE_LOCATION,
            desc: 'Toggle speak row location'
        },
        {
            keyCode: 53,
            func: speakCurrRowAndCol,
            cmd: Command.SPEAK_ROW_COL,
            desc: 'Speak row and column'
        },
        {
            keyCode: 54,
            func: toggleSpeakDisplacement,
            cmd: Command.TOGGLE_DISPLACEMENT,
            desc: 'Toggle speak displacement'
        },
        {
            keyCode: 55,
            func: focus,
            cmd: Command.FOCUS_TEXT,
            desc: 'Focus text'
        }
    ];
    var onFocus = function (_, editor) {
        cvoxAce.editor = editor;
        editor.getSession().selection.on('changeCursor', onCursorChange);
        editor.getSession().selection.on('changeSelection', onSelectionChange);
        editor.getSession().on('change', onChange);
        editor.getSession().on('changeAnnotation', onAnnotationChange);
        editor.on('changeStatus', onChangeStatus);
        editor.on('findSearchBox', onFindSearchbox);
        editor.container.addEventListener('keydown', onKeyDown);

        lastCursor = editor.selection.getCursor();
    };
    var init = function (editor) {
        onFocus(null, editor);
        SHORTCUTS.forEach(function (shortcut) {
            keyCodeToShortcutMap[shortcut.keyCode] = shortcut;
            cmdToShortcutMap[shortcut.cmd] = shortcut;
        });

        editor.on('focus', onFocus);
        if (isVimMode()) {
            cvox.Api.setKeyEcho(false);
        }
        initContextMenu();
    };

    function cvoxApiExists() {
        return (typeof (cvox) !== 'undefined') && cvox && cvox.Api;
    }

    var tries = 0;
    var MAX_TRIES = 15;

    function watchForCvoxLoad(editor) {
        if (cvoxApiExists()) {
            init(editor);
        } else {
            tries++;
            if (tries >= MAX_TRIES) {
                return;
            }
            window.setTimeout(watchForCvoxLoad, 500, editor);
        }
    }

    var Editor = require('../editor').Editor;
    require('../config').defineOptions(Editor.prototype, 'editor', {
        enableChromevoxEnhancements: {
            set: function (val) {
                if (val) {
                    watchForCvoxLoad(this);
                }
            },
            value: true // turn it on by default or check for window.cvox
        }
    });

});
(function () {
    window.require(["ace/ext/chromevox"], function () {
    });
})();
            